package network

import (
	"Alpaca/log"
	"net"
	"sync"
	"time"
)

type TcpClient struct {
	//conf
	Addr            string //远端地址
	ConnNum         int
	WriteChanLen    int
	MinMsgLen       uint32
	MaxMsgLen       uint32
	ConnectInterval time.Duration
	AutoReConnect   bool

	//net
	conns     ConnSet
	connWg    sync.WaitGroup
	closeFlag bool
	NewAgent  func(*TcpConn) Agent

	msgParser *MsgParser
	sync.Mutex
}

func (client *TcpClient) Start() {
	client.init()

	for i := 0; i < client.ConnNum; i++ {
		client.connWg.Add(1)
		go client.connect()
	}
}

func (client *TcpClient) init() {
	client.Lock()
	defer client.Unlock()

	if client.ConnNum <= 0 {
		client.ConnNum = 1
		log.Release("ConnNum is invalid,reset to %v", client.ConnNum)
	}
	if client.WriteChanLen <= 0 {
		client.WriteChanLen = 100
		log.Release("WriteChanLen is invalid,reset to %v", client.WriteChanLen)
	}
	if client.ConnectInterval <= 0 {
		client.ConnectInterval = time.Second * 3
		log.Release("ConnectInterval is invalid,reset to %v", client.ConnectInterval)
	}
	if client.NewAgent == nil {
		log.Fatal("NewAgent could not be nil")
	}
	if client.conns != nil {
		log.Fatal("Client is running")
	}

	client.conns = make(ConnSet)
	client.closeFlag = false

	msgParser := NewParser()
	msgParser.SetMsgLen(client.MinMsgLen, client.MaxMsgLen)
	client.msgParser = msgParser
}

func (client *TcpClient) connect() {
	log.Debug("client is running")
	defer client.connWg.Done()

reconnect:
	conn := client.dial() //连接
	if conn == nil {
		return
	}

	client.Lock()
	if client.closeFlag {
		client.Unlock()
		conn.Close()
		return
	}

	client.conns[conn] = struct{}{}
	client.Unlock()

	tcpConn := NewTcpConn(conn, client.WriteChanLen, client.msgParser)
	agent := client.NewAgent(tcpConn)
	agent.Run()

	//agent结束clean up
	client.Lock()
	delete(client.conns, conn)
	client.Unlock()
	tcpConn.Close()
	agent.OnClose()

	if client.AutoReConnect { //重连
		time.Sleep(client.ConnectInterval)
		goto reconnect
	}
}

func (client *TcpClient) dial() net.Conn {
	for {
		conn, err := net.Dial("tcp", client.Addr)
		if err == nil || client.closeFlag {
			return conn
		}

		log.Release("connect to %v error: %v", client.Addr, err)
		time.Sleep(client.ConnectInterval)
		continue
	}
}

func (client *TcpClient) Close() {
	client.Lock()
	client.closeFlag = true
	for conn := range client.conns {
		conn.Close()
	}
	client.conns = nil
	client.Unlock()

	client.connWg.Wait()
}
